package com.lyn.anr

import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.AdviceAdapter
import java.util.UUID

/**
 *
 * @author longyn 2025/07/14
 * @version 1.0.0
 */
class AnrClassVisitor(cv: ClassVisitor) : ClassVisitor(Opcodes.ASM9, cv) {

    private var className: String = ""

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        className = name ?: ""
        super.visit(version, access, name, signature, superName, interfaces)
    }

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        //确保没有插桩 interface 或 abstract 方法
        if ((access and Opcodes.ACC_ABSTRACT) != 0 || (access and Opcodes.ACC_NATIVE) != 0) {
            return super.visitMethod(access, name, descriptor, signature, exceptions)
        }
        if ("onInvoke" == name){
            return super.visitMethod(access, name, descriptor, signature, exceptions)
        }
        val visitMethod = super.visitMethod(access, name, descriptor, signature, exceptions)
        val uuid = UUID.randomUUID().toString().replace("-", "")
        try {
            return object : AdviceAdapter(Opcodes.ASM9, visitMethod, access, name, descriptor) {
                private var startTimeIndex = -1
                private var timeConsuming = -1

                override fun onMethodEnter() {
                    super.onMethodEnter()
                    try {
                        startTimeIndex = newLocal(Type.LONG_TYPE)
                        visitMethodInsn(
                            Opcodes.INVOKESTATIC,
                            "java/lang/System",
                            "currentTimeMillis",
                            "()J",
                            false
                        )
                        storeLocal(startTimeIndex, Type.LONG_TYPE)
                    }catch (_:Throwable){
                    }
                }

                override fun onMethodExit(opcode: Int) {
                    super.onMethodExit(opcode)
                    try {
                        if (opcode != Opcodes.ATHROW) {
                            visitMethodInsn(
                                Opcodes.INVOKESTATIC,
                                "java/lang/System",
                                "currentTimeMillis",
                                "()J",
                                false
                            )
                            loadLocal(startTimeIndex, Type.LONG_TYPE)
                            visitInsn(Opcodes.LSUB)
                            timeConsuming = newLocal(Type.LONG_TYPE)
                            storeLocal(timeConsuming, Type.LONG_TYPE)
                            visitLdcInsn(uuid)
                            visitLdcInsn(className)
                            visitLdcInsn(name ?: "")
                            loadLocal(timeConsuming, Type.LONG_TYPE)
                            visitMethodInsn(
                                Opcodes.INVOKESTATIC,
                                "com/lyn/anr/core/Anr",
                                "invoke",
                                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V",
                                false
                            )
                        }
                    }catch (_:Throwable){

                    }
                }
            }
        }catch (e:Throwable){
            return super.visitMethod(access, name, descriptor, signature, exceptions)
        }
    }
}